package com.esdk.sql.orm;

import com.esdk.esdk;
import com.esdk.sql.SQLAssistant;
import com.esdk.sql.SQLRuntimeException;
import com.esdk.sql.datasource.ConnectionPoolBuilder;
import com.esdk.sql.datasource.IConnectionPool;
import com.esdk.sql.datasource.EasyConnectionPool;
import com.esdk.utils.ThreadUtils;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/***
 * @author 范飞宇
 * @since 2019.12.5
 */
public class ConnectionUtils{
	private static IConnectionPool connectionPool;
	private static Connection singleConn=null;
	private static int singleConnectionId=0;
	public static final ConcurrentHashMap<Long,Connection> connMap=new ConcurrentHashMap<>();

	public static void setConnectionPool(String propertiesFileName){
		connectionPool=ConnectionPoolBuilder.createPool(esdk.prop.getProfileInputStream(propertiesFileName));
	}

	public static IConnectionPool getConnectionPool(){
		if(connectionPool==null)
			connectionPool=ConnectionPoolBuilder.createPool(esdk.prop);
		return connectionPool;
	}

	public void printStatus(){
		connectionPool.printStatus();
	}

	public static long getRequestId(){
		return EasyConnectionPool.getRequestId();
	}

	/**单例连接永不过期*/
	public static Connection getSingletonConnection(){
		try{
			if(singleConn!=null&&esdk.sql.isClosed(singleConn)){
				esdk.sql.close(singleConn);
				singleConn=null;
			}
			if(singleConn==null){
				singleConn=getConnection(singleConnectionId,true,-1);
			}
		}catch(Exception e){
			esdk.error(e);
		}
		return singleConn;
	}

	public static void setDataSource(DataSource ds){
		connectionPool.setDatasource(ds);
	}

	public static DataSource getDataSource(){
		return connectionPool.getDataSource();
	}

	public static synchronized Connection getConnection(){
		return getConnection(getRequestId(),true,null);
	}
	public static synchronized Connection getConnection(boolean isAutoCommit){
		return getConnection(getRequestId(),isAutoCommit,null);
	}

	public static synchronized Connection getConnection(Integer expireSec){
		return getConnection(getRequestId(),true,expireSec);
	}

	public static synchronized Connection getConnection(long requestId,boolean isAutoCommit){
		return getConnection(getRequestId(),true,null);
	}
	/**注意：connection从datasource获取后都会确保是非事务的，以防止之前开启过事务没有关闭
	 * @param expireSec 设定过期自动关闭连接时间(单位秒)，<=0则不过期，null为默认过期
	 * */
	public static synchronized Connection getConnection(long requestId,boolean isAutoCommit,Integer expireSec){
		Connection conn=connMap.get(requestId);
		try{
			if(conn==null||conn.isClosed()){
				conn=expireSec==null?getConnectionPool().getConnection():getConnectionPool().getConnection(expireSec);
				connMap.put(requestId,conn);
			}
			conn.setAutoCommit(isAutoCommit);
		}catch(SQLException e){
			throw esdk.tool.wrapThrowble(e);
		}
		return conn;
	}

	public static void setConnection(Connection connection){
		long requestId=getRequestId();
		if(ifUsedConnection())
			closeCurrentConnection();
		connMap.put(requestId,connection);
	}

	public static boolean ifUsedConnection() {
		return connMap.containsKey(getRequestId());
	}

	public static void setAutoCommit(boolean autoCommit){
		if(ifUsedConnection()){
			try{
				getConnection().setAutoCommit(autoCommit);
			}catch(SQLException e){
				throw esdk.tool.wrapThrowble(e);
			}
		}
	}

	public static void commit(boolean autoCommit){
		if(ifUsedConnection()){
			try{
				getConnection().commit();
			}catch(SQLException e){
				throw esdk.tool.wrapThrowble(e);
			}
		}
	}

	public static boolean isUsingConnection(Long requestId) {
		return connMap.containsKey(requestId);
	}

	public static boolean isUsingConnection(Connection conn) {
		return connMap.containsValue(conn);
	}

	/**关闭数据库连接
	 * @param isRollback 关闭前是否自动回滚
	 * */
	public static void closeCurrentConnection(boolean isRollback){
		if(ifUsedConnection()){
			long requestId=getRequestId();
			Connection conn=connMap.remove(requestId);
			if(conn!=null){
				try{
					if(isRollback&&!conn.getAutoCommit()){
						conn.rollback();
						esdk.warn("数据库已回滚，RequestId:",requestId);
					}
//					long startCall=Long.valueOf(conn.getClientInfo("start_call"));
					esdk.sql.close(conn);
//					esdk.sout("已关闭数据库连接，耗时{}，RequestId:{}",esdk.time.formatElapse(System.currentTimeMillis()-startCall),requestId);
				}catch(Exception e){
					esdk.error(e);
				}
			}
		}
	}
	/**请求处理结束后要关闭数据库链接，非常重要！*/
	public static void closeCurrentConnection(){
		closeCurrentConnection(false);
	}
	public static void closeConnection(long requestId){
		Connection conn=connMap.remove(requestId);
		if(conn!=null){
//			long startCall=Long.valueOf(conn.getClientInfo("start_call"));
			esdk.sql.close(conn);
//			esdk.sout("已关闭数据库连接，耗时{}，RequestId:{}",esdk.time.formatElapse(System.currentTimeMillis()-startCall),requestId);
		}
	}
	public static void closeAllConnections(){
		for(Long requestId:connMap.keySet()){
			closeConnection(requestId);
		}
	}

	public static void main(String[] args) throws SQLException, IOException{
		Long requestId=(Long)esdk.reflect.invokeMethod(null,"com.esdk.sql.orm.ConnectionUtils.getRequestId");
		esdk.sout(requestId);
		ConnectionUtils.setConnectionPool("db.properties");
		final LinkedList<Connection> list=new LinkedList<>();
		EasyConnectionPool.excludePackageNames=esdk.regex.toAnyMatch(esdk.str.split(EasyConnectionPool.class.getSimpleName()));
		EasyConnectionPool.isPrintStatus=true;
		ThreadUtils.async(()->{
			for(int i=0;i<20;i++){
				Connection conn=ConnectionUtils.getConnection();
				list.add(conn);
				esdk.sout(i+1);
				connectionPool.printStatus();
			};
			esdk.sout("创建数据库连接池的测试线程已结束");
		});
		esdk.tool.sleep(300);
		ThreadUtils.async(()->{
			while(!list.isEmpty()){
				esdk.sql.close(list.pollFirst());
				esdk.tool.sleep(1000);
			}
			esdk.info("关闭数据库池接池的测试线程已完成，所有连接已关闭");
		});
		ThreadUtils.awaitTermination(5, TimeUnit.SECONDS,false);
		ConnectionUtils.getConnection();
		connectionPool.printStatus();
		esdk.info("getCallers:"+connectionPool.getCallers());
		esdk.info("getAllCallStacks:"+connectionPool.getAllCallStacks());
		connectionPool.close();
	}
}
